package jsecurity.encryption;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.NoSuchPaddingException;

import jsecurity.JSecurity;
import jsecurity.exceptions.CipherException;
import jsecurity.utils.Base64Codec;

public class Cipher
{
	private static enum CipherMode
	{
		ENCRYPT_MODE, DECRYPT_MODE
	}
	
	private Base64Codec base64;
	private javax.crypto.Cipher cipher;
	
	public Cipher() throws CipherException
	{
		try 
		{
			this.cipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPWithMD5AndMGF1Padding");
			this.base64 = new Base64Codec();
		}
		catch(NoSuchAlgorithmException e)
		{
			throw new CipherException("It was not possible to create a cipher for the RSA algorithm", e);
		} 
		catch(NoSuchPaddingException e) 
		{
			throw new CipherException("It was not possible to create a cipher with the specified padding method", e);
		}
	}

	public String encrypt(String plainText, Key encryptionKey) throws CipherException
	{
		return execute(plainText, encryptionKey, CipherMode.ENCRYPT_MODE);
	}

	public String decrypt(String encryptedText, Key decryptionKey) throws CipherException
	{
		return execute(encryptedText, decryptionKey, CipherMode.DECRYPT_MODE);
	}
	
	private synchronized String execute(String input, Key key, CipherMode cipherMode) throws CipherException
	{		
		if(input == null || input.equals("")) return input;
		if(key == null) throw new CipherException("The encryption key must be defined", new KeyException("Key is null"));
		
		try
		{
			if(cipherMode == CipherMode.ENCRYPT_MODE)
			{
				this.cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key);
				
				byte[] encrypted = this.cipher.doFinal(input.getBytes(JSecurity.DEFAULT_CHARSET));
				return this.base64.encodeBytes(encrypted, Base64Codec.GZIP);
			}
			else
			{
				this.cipher.init(javax.crypto.Cipher.DECRYPT_MODE, key);
				
				byte[] decrypted = this.cipher.doFinal(this.base64.decode(input));
				return new String(decrypted, JSecurity.DEFAULT_CHARSET);
			}
		}
		catch(InvalidKeyException e) 
		{
			if(cipherMode == CipherMode.ENCRYPT_MODE) throw new CipherException("The supplied key for ENCRYPTION if not valid", e);
			else throw new CipherException("The supplied key for DECRYPTION if not valid", e);
		}
		catch(IOException e)
		{
			throw new CipherException("It was not possible to decode the input from Base64", e);
		}
		catch(Exception e) 
		{
			throw new CipherException("It was not possible to execute the cipher procedures", e);
		}
	}
}
